/*
 * Copyright © 2024-2025 Wenze Wei
 *
 * This file is part of StadionOS.
 *
 * This Source Code Form is subject to the terms of the GNU General Public License, v. 2.0.
 * You can obtain a copy of the GPL at
 * https://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 */

#include "../include/vsprint.h"

static char buffer[MAX_BUFFER_SIZE];

static void convert_int(char *buf, int num, int base, int flags, int width, int precision) {
    char c, sign;
    char tmp[MAX_BUFFER_SIZE];
    char *p;
    int i = 0;

    sign = 0;
    if (flags & SIGN && (sign = num < 0) != 0)
        num = -num;

    p = tmp + MAX_BUFFER_SIZE - 1;
    *p = '\0';
    do {
        c = (sign ? -num : num) % base + '0';
        if (c > '9')
            c += SMALL - '0';
        *--p = c;
        num /= base;
    } while (num > 0);

    width -= (tmp + MAX_BUFFER_SIZE - 1 - p);
    if (!(flags & (LEFT | ZEROPAD)))
        while (width-- > 0)
            *--p = ' ';
    if (sign)
        *--p = '-';
    else if (flags & PLUS)
        *--p = '+';
    else if (flags & SPACE)
        *--p = ' ';

    if (flags & SPECIAL) {
        if (flags & ZEROPAD && width > 0) {
            width--;
            *--p = (base == 8) ? '0' : (base == 16) ? "0x"[flags & SMALL] : 0;
        }
        if (base == 8 || (base == 16 && !(flags & SMALL)))
            width -= 2;
    }

    while ((flags & ZEROPAD) && width-- > 0)
        *--p = '0';

    while (width-- > 0)
        *--p = ' ';

    while (*p)
        *buf++ = *p++;
    *buf = '\0';
}

static void convert_float(char *buf, double num, int flags, int width, int precision, char fmt) {
    char tmp[MAX_BUFFER_SIZE];
    char *p = tmp;
    int len;

    if (isnan(num)) {
        strcpy(buf, "nan");
        return;
    }
    if (isinf(num)) {
        strcpy(buf, (num < 0) ? "-inf" : "inf");
        return;
    }

    snprintf(tmp, sizeof(tmp), "%.*f", precision, num);

    len = strlen(tmp);
    if (!(flags & LEFT)) {
        while (len < width--)
            *buf++ = ' ';
    }
    while (*p)
        *buf++ = *p++;
    while (len < width--)
        *buf++ = ' ';
    *buf = '\0';
}

/**
 * @brief Converts formatted input to a plain string.
 *
 * This function converts formatted input to a plain string according to the format string.
 * It supports various format specifiers including integers, floats, strings, and characters.
 *
 * @param buf The buffer to store the converted string.
 * @param fmt The format string.
 * @param args The argument list.
 * @return int The length of the converted string.
 */
int Svsprint(char *buf, const char *fmt, va_list args) {
    int len, i, flags, field_width, precision, qualifier;
    char *str, *s;
    int num;
    unsigned int unum;
    double fnum;

    for (str = buf; *fmt; ++fmt) {
        if (*fmt != '%') {
            *str++ = *fmt;
            continue;
        }

        flags = 0;
        repeat:
            ++fmt;
            switch (*fmt) {
                case '-': flags |= LEFT; goto repeat;
                case '+': flags |= PLUS; goto repeat;
                case ' ': flags |= SPACE; goto repeat;
                case '#': flags |= SPECIAL; goto repeat;
                case '0': flags |= ZEROPAD; goto repeat;
            }

        field_width = -1;
        if (*fmt == '*') {
            field_width = va_arg(args, int);
            fmt++;
        } else if (isdigit(*fmt)) {
            field_width = *fmt++ - '0';
            while (isdigit(*fmt))
                field_width = field_width * 10 + *fmt++ - '0';
        }

        precision = -1;
        if (*fmt == '.') {
            fmt++;
            if (*fmt == '*') {
                precision = va_arg(args, int);
                fmt++;
            } else if (isdigit(*fmt)) {
                precision = *fmt++ - '0';
                while (isdigit(*fmt))
                    precision = precision * 10 + *fmt++ - '0';
            }
        }

        qualifier = -1;
        if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
            qualifier = *fmt++;
            if (qualifier == 'l' && *fmt == 'l') {
                qualifier = 'L';
                fmt++;
            }
        }

        switch (*fmt) {
            case 'c':
                if (!(flags & LEFT)) {
                    while (--field_width > 0)
                        *str++ = ' ';
                }
                *str++ = (char)va_arg(args, int);
                while (--field_width > 0)
                    *str++ = ' ';
                break;

            case 's':
                s = va_arg(args, char *);
                if (!s)
                    s = "(null)";
                len = strlen(s);
                if (precision >= 0 && len > precision)
                    len = precision;
                if (!(flags & LEFT)) {
                    while (len < field_width--)
                        *str++ = ' ';
                }
                for (i = 0; i < len; ++i) {
                    *str++ = *s++;
                }
                while (len < field_width--)
                    *str++ = ' ';
                break;

            case 'd':
            case 'i':
                num = va_arg(args, int);
                convert_int(str, num, 10, flags | SIGN, field_width, precision);
                str += strlen(str);
                break;

            case 'o':
                unum = va_arg(args, unsigned int);
                convert_int(str, unum, 8, flags, field_width, precision);
                str += strlen(str);
                break;

            case 'u':
                unum = va_arg(args, unsigned int);
                convert_int(str, unum, 10, flags, field_width, precision);
                str += strlen(str);
                break;

            case 'x':
                unum = va_arg(args, unsigned int);
                convert_int(str, unum, 16, flags | SMALL, field_width, precision);
                str += strlen(str);
                break;

            case 'X':
                unum = va_arg(args, unsigned int);
                convert_int(str, unum, 16, flags & (~SMALL), field_width, precision);
                str += strlen(str);
                break;

            case 'e':
            case 'E':
            case 'f':
            case 'g':
            case 'G':
                fnum = va_arg(args, double);
                convert_float(str, fnum, flags, field_width, precision, *fmt);
                str += strlen(str);
                break;

            case 'p':
                unum = (uintptr_t)va_arg(args, void *);
                convert_int(str, unum, 16, flags | SMALL, field_width, precision);
                str += strlen(str);
                break;

            case 'n':
                num = str - buf;
                *(va_arg(args, int *)) = num;
                break;

            case '%':
                *str++ = '%';
                break;

            default:
                *str++ = '%';
                *str++ = *fmt;
                break;
        }
    }
    *str = '\0';

    return str - buf;
}
